#include "ThreadCache.hpp"
#include "CentralCache.hpp"

// 如果ThreadCache没有空间了，则需要从Central Cache获取空间
void *ThreadCache::GetSpaceFromCentral(size_t size, size_t index)
{
    // 那么我们应该向Central申请多大的内存呢？最少要申请一个内存块的大小，即申请size大小的空间
    // 但是我们不能每次只向Central Cache当中申请一个内存块，这样会造成效率的下降
    // 应该在保证ThreadCache正常运行的条件下，多申请一部分空间，那么多的这一部分空间应该怎么确定呢？
    // 我们结合网络拥塞控制算法，设计一个慢反馈调节算法
    size_t blockNum = std::min(_freelist[index].GetMaxSize(), SizeClass::CacheBlockNum(size));
    // 这里的blocknum表示期望从中心缓存当中获取的内存块数量
    if (blockNum == _freelist[index].GetMaxSize())
    {
        _freelist[index].GetMaxSize()++;
    }

    // star指向获取的内存块的起始地址，end指向获取的内存块的结尾位置
    void *start = nullptr;
    void *end = nullptr;
    size_t altualBlockNum = CentralCache::GetInstance()->AllocRangeMemory(start, end, blockNum, size);
    // 我们需要判断一下返回结果是否是正确的
    assert(altualBlockNum > 0);
    // 如果只有一个内存块，则直接返回给上层使用
    if (altualBlockNum == 1)
    {
        assert(start == end);
        return start;
    }
    else
    {
        // 如果申请了多个内存块，则需要将第一个内存块的地址返回给上层使用，其余内存块头插进入自由链表
        _freelist[index].PushRange(NextObj(start), end, altualBlockNum);
        return start;
    }
}

// 按字节为单位申请内存的接口
void *ThreadCache::AllocMemory(size_t size)
{
    // 首先我们要知道我们要申请的内存在哪个桶，即找到内存空间的索引
    size_t index = SizeClass::Index(size);
    // 其次，我们需要知道当前申请的内存对齐后的大小
    size_t alignsize = SizeClass::AlignSize(size);
    if (!_freelist[index].Empty())
    {
        // 如果当前位置的桶下面挂的自由链表不为空，直接将地址返回给上层使用
        return _freelist[index].Pop();
    }
    else
    {
        // 如果为空，则需要向下一层的中心缓存Central cache申请空间
        // 由于下一层中心缓存的hash映射结构与当前相同，所以我们需要传递当前的索引index和对齐后的大小alignsize
        return GetSpaceFromCentral(alignsize, index);
    }
}

// 当链表太长时，将自由链表的节点交给中心缓存
void ThreadCache::TruncRangeList(FreeList&freelist,size_t size)
{
    void*start=nullptr;
    void*end=nullptr;
    freelist.PopRange(start,end,freelist.GetMaxSize());
    //回收内存
    CentralCache::GetInstance()->RecycleThreadCacheMemory(start,size);
}

// 归还内存的接口
// obj是要释放的空间的起始地址，size是空间的大小，单位是字节
void ThreadCache::RecycleMemory(void *obj, size_t size)
{
    assert(obj);
    // 将节点链入自由链表即完成了内存回收的工作
    size_t index = SizeClass::Index(size);
    _freelist[index].Push(obj);

    // 如果桶下面自由链表过长，则执行内存回收机制
    // 如何判断链表过长呢？当节点的个数大于允许的最大节点的个数时，执行内存回收机制
    if (_freelist[index].GetSize() > _freelist[index].GetMaxSize())
    {
        //截取链表
        TruncRangeList(_freelist[index], size);
    }
}

//为了提高效率，我们需要让ThreadCache支持多线程
//申明一个静态的TLS，这样可以让每一个ThreadCache成为一个线程
static thread_local ThreadCache* pTLSThreadCache = nullptr;